using System;
using System.Collections.Generic;
using System.Text;
using Zsa.Emulation.Memory;
using Zsa.Emulation.Util;

namespace Zsa.Emulation.PPU
{
    /// <summary>
    /// Maps the video register to cpu memory.
    /// </summary>
    public class PpuCpuMemoryMapper : IMemoryStream
    {
        #region Fields
        /// <summary>
        /// The machine this is connected to.
        /// </summary>
        private NesMachine _Machine;
        /// <summary>
        /// The ppu
        /// </summary>
        private PictureProcessingUnit _Ppu;

        /// <summary>
        /// The cpu ram.
        /// </summary>
        private Processor.CpuMemoryMap _CpuRam;
        #endregion Fields

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="T:PpuCpuMemoryMapper"/> class.
        /// </summary>
        /// <param name="ppu">The ppu.</param>
        public PpuCpuMemoryMapper(PictureProcessingUnit ppu)
        {
            _Ppu = ppu;
            
        }
        #endregion Constructor

        #region Methods

        #region Read Methods
        /// <summary>
        /// Read a byte from the ppu.
        /// </summary>
        /// <param name="address">The address to read.</param>
        /// <returns>The byte value.</returns>
        public byte Read(int address)
        {
            
            
            if (address < 0x2000)
            {
                int i = address - ((address / 8) * 8);
                address = i;
            }
            switch (address)
            {
                // The ppu control register one.
                //case 0x2000:
                case 0:
                    return (byte)_Ppu.RegisterControlOne;
                case 1:
                //case 0x2001:
                    return (byte)_Ppu.RegisterControlTwo;
                case 2:
                //case 0x2002:
                    return (byte)_Ppu.RegisterStatus;
                case 3:
                case 0x2003:
                    //throw new Exception();
                    return 0;
                    break;
                case 4:
                //case 0x2004:
                    return _Ppu.RegisterSpriteMemoryData;
                case 5:
                //case 0x2005:
                    return 0;
                    throw new Exception();
                case 6:
                //case 0x2006:
                    return 0;
                    throw new Exception();
                case 7:
                //case 0x2007:
                    return _Ppu.RegisterPpuMemoryData;
                // Sprite dma.
                case 0x4014:
                    return 0;
                default:
                    throw new ArgumentOutOfRangeException(
                        "address", address,
                        string.Format("The ppu address is out of range ! Vaue {0}. ", address)
                    );

            }
        }

        /// <summary>
        /// Reads the specified address.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <returns></returns>
        public byte Read(ushort address)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Reads the specified address.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <returns></returns>
        public ushort ReadUInt16(int address)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Reads the specified address.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <returns></returns>
        public ushort ReadUInt16(ushort address)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Reads the specified buffer.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <param name="address">The address.</param>
        /// <param name="count">The count.</param>
        public void Read(byte[] buffer, int address, int count)
        {

            //if (address < 0x2008 || address > 0x1FFF) 
            //{
            //    return 0;    
            //}
            //int _Count = EndAddress - count;
            //_Count = count > _Count ? _Count : count;
            //return _Count;
        }

        #endregion Read Methods

        #region Write Methods
        /// <summary>
        /// Writes the specified buffer.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <param name="address">The address.</param>
        /// <param name="count">The count.</param>
        public void Write(byte[] buffer, int address, int count)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Writes the specified address.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <param name="romByte">The rom byte.</param>
        public void Write(int address, byte romByte)
        {
            try
            {
                switch (address)
                {
                    // The ppu control register one.
                    case 0:
                        //case 0x2000:
                        _Ppu.RegisterControlOne = romByte;
                        break;
                    case 1:
                        //case 0x2001:
                        _Ppu.RegisterControlTwo = romByte;
                        break;
                    case 2:
                        //case 0x2002:
                        throw new Exception();
                        break;
                    case 3:
                        //case 0x2003:
                        _Ppu.RegisterSpriteMemoryAddress = romByte;
                        

                        break;
                    case 4:
                        //case 0x2004:
                        _Ppu.RegisterSpriteMemoryData = romByte;
                        break;
                    case 5:
                        //case 0x2005:
                        _Ppu.RegisterScreenScrollOffsets = romByte;
                        break;
                    case 6:
                        //case 0x2006:
                        _Ppu.RegisterPpuMemoryAddress = romByte;
                        break;
                    case 7:
                        //case 0x2007:
                        _Ppu.RegisterPpuMemoryData = romByte;
                        break;

                    case 0x4014:
                        // If write to DMA register then write contents of 
                        // main memory from 0x100 * romByte
                        _CpuRam.DoDma(romByte);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException(
                            "address", address,
                            string.Format("The ppu address is out of range ! Vaue {0}. Data {1}", address, romByte)
                        );
                }
            }
            catch (Exception excep)
            {
                throw excep;
            }
            
        }

        #endregion Write Methods

        #endregion Methods

        #region Properties

        /// <summary>
        /// Gets the length of the stream.
        /// </summary>
        /// <value>The size.</value>
        public int Length
        {
            get
            {
                return 9;
            }
        }

        internal byte[] SpriteMemory
        {
            get
            {
                return _Ppu.SpriteRam;
            }
            set
            {
                _Ppu.SpriteRam = value;
            }
        }

        internal Processor.CpuMemoryMap MainMem
        {
            get
            {
                return _CpuRam;
            }
            set
            {
                _CpuRam = value;
            }
        }

        #endregion Properties
    }
}
